package cn.tedu.mall.seckill.timer;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.pojo.seckill.model.SeckillSku;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.seckill.mapper.SeckillSkuMapper;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SeckillInitialJob implements Job {
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;
    @Autowired
    private SeckillSkuMapper seckillSkuMapper;
    //RedisTemplate在保存对象时，会将对象序列化成二进制格式，再将二进制格式保存在Redis中，
    //这样做读写速度快，但是不能修改Redis数据
    @Autowired
    private RedisTemplate redisTemplate;//保存随机码
    //sku的库存数，我们在修改时需要在Redis中进行修改，能避免超卖的情况发生
    //库存数是数字，Redis支持直接对Redis内部数字的数据进行修改，无需java代码
    //这就需要我们将库存数以字符串的形式保存到Redis中
    //StringRedisTemplate在保存对象时是以字符串的形式保存进去
    @Autowired
    private StringRedisTemplate stringRedisTemplate;//保存库存数

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //缓存预热--是在秒杀开始前进行数据的缓存
        //设计秒杀开始前5分钟进行预热
        //所以我们需要一个五分钟后的时间去检查秒杀是否开始
        LocalDateTime time = LocalDateTime.now().plusMinutes(5);
        //查询这个时间的所有秒杀商品
        List<SeckillSpu> seckillSpus = seckillSpuMapper.findSeckillSpusByTime(time);
        //seckillSpus集合如果为null就说明不在秒杀时间范围内，不预热
        if(seckillSpus==null)
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"不在秒杀时间范围内");
        //seckillSpus不为null--进行预热
        //遍历seckillSpus集合，根据spuId得到sku的列表，然后将库存数进行缓存
        for (SeckillSpu seckillSpu : seckillSpus) {
            Long spuId = seckillSpu.getSpuId();
            List<SeckillSku> seckillSkus = seckillSkuMapper.findSeckillSkusBySpuId(spuId);
            //将当前seckillSkus集合中对应的库存数进行缓存预热
            for (SeckillSku seckillSku : seckillSkus) {
                Long skuId = seckillSku.getSkuId();
                log.info("开始将{}号商品的库存数预热到Redis中",skuId);
                //根据skuId获取到Redis中对应的key
                String stockKey = SeckillCacheUtils.getStockKey(skuId);
                //检查Redis中是否已经包含这个key
                if(redisTemplate.hasKey(stockKey)){
                    log.info("{}号商品已经在缓存中了",skuId);
                }else{
                    //如果key不存，那么就需要将当前的库存数缓存到Redis中
                    stringRedisTemplate.boundValueOps(stockKey).set(seckillSku.getSeckillStock()+"",
                          24*60*60+ RandomUtils.nextInt(100), TimeUnit.SECONDS );
                    log.info("成功为{}号商品添加库存数",skuId);
                }
            }
            //随机码的作用：想要购买这个商品，需要通过控制层的url访问
            //例如：/seckill/spu/{id}，如果{id}为真实id容易被猜测到，就可以投机购买
            //这样不合理，所以我们将{id}位置的值设置成一个随机的数字，猜中的几率会大大降低
            //也是秒杀开始前5分钟才有这个随机码，有了随机码才能购买这个商品
            //获取随机码的key值
            String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
            //判断随机码是否已经存在
            if(!redisTemplate.hasKey(randCodeKey)){
                //随机码的key为null，需要生成随机码，并需要保存到url属性中
                //生成随机码
             int randomCode =  RandomUtils.nextInt(900000)+100000;
             redisTemplate.boundValueOps(randCodeKey).set(randomCode,
                     24*60*60+RandomUtils.nextInt(100),TimeUnit.SECONDS);
             log.info("成为spuId为{}商品生成随机码，随机码为{}",spuId,randomCode);
            }else{
                //如果随机码已经存在，取出来，方便测试
                int randomCode = (int) redisTemplate.boundValueOps(randCodeKey).get();
                log.info("spuId为{}的商品的随机码为{}",spuId,randomCode);
            }
        }
    }
}
